//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the MIT License.
//    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
//    ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
//    TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
//    PARTICULAR PURPOSE AND NONINFRINGEMENT.
//
//*********************************************************
//! @file
//! Helpers for common patterns and tasks when using C++/WinRT.

#ifndef __WIL_CPPWINRT_HELPERS_DEFINED
#define __WIL_CPPWINRT_HELPERS_DEFINED

/// @cond
namespace wil::details
{
struct dispatcher_RunAsync
{
    template <typename Dispatcher, typename... Args>
    static void Schedule(Dispatcher const& dispatcher, Args&&... args)
    {
        dispatcher.RunAsync(std::forward<Args>(args)...);
    }
};

struct dispatcher_TryEnqueue
{
    template <typename Dispatcher, typename... Args>
    static void Schedule(Dispatcher const& dispatcher, Args&&... args)
    {
        dispatcher.TryEnqueue(std::forward<Args>(args)...);
    }
};

template <typename Dispatcher>
struct dispatcher_traits;
} // namespace wil::details

#if defined(_RESUMABLE_FUNCTIONS_SUPPORTED)
#include <experimental/coroutine>
namespace wil::details
{
template <typename T = void>
using coroutine_handle = std::experimental::coroutine_handle<T>;
}
#elif defined(__cpp_impl_coroutine)
#include <coroutine>
#if (__cpp_lib_coroutine >= 201902L)
namespace wil::details
{
template <typename T = void>
using coroutine_handle = std::coroutine_handle<T>;
}
#endif // __cpp_lib_coroutine
#endif // __cpp_impl_coroutine
/// @endcond

#if defined(_RESUMABLE_FUNCTIONS_SUPPORTED) || \
    (defined(__cpp_impl_coroutine) && defined(__cpp_lib_coroutine) && (__cpp_lib_coroutine >= 201902L))
/// @cond
namespace wil::details
{
struct dispatched_handler_state
{
    details::coroutine_handle<> handle{};
    bool orphaned = false;
};

struct dispatcher_handler
{
    dispatcher_handler(dispatched_handler_state* state) : m_state(state)
    {
    }
    dispatcher_handler(dispatcher_handler&& other) noexcept : m_state(std::exchange(other.m_state, {}))
    {
    }

    ~dispatcher_handler()
    {
        if (m_state && m_state->handle)
        {
            m_state->orphaned = true;
            Complete();
        }
    }
    void operator()()
    {
        Complete();
    }

    void Complete()
    {
        auto state = std::exchange(m_state, nullptr);
        std::exchange(state->handle, {}).resume();
    }

    dispatched_handler_state* m_state;
};
} // namespace wil::details
/// @endcond

namespace wil
{
//! Resumes coroutine execution on the thread associated with the dispatcher, or throws
//! an exception (from an arbitrary thread) if unable. Supported dispatchers are
//! Windows.System.DispatcherQueue, Microsoft.System.DispatcherQueue,
//! Microsoft.UI.Dispatching.DispatcherQueue, and Windows.UI.Core.CoreDispatcher,
//! but you must include the corresponding <winrt/Namespace.h> header before including
//! wil/cppwinrt_helpers.h. It is okay to include wil/cppwinrt_helpers.h multiple times:
//! support will be enabled for any winrt/Namespace.h headers that were included since
//! the previous inclusion of wil/cppwinrt_headers.h.
template <typename Dispatcher>
[[nodiscard]] auto resume_foreground(
    Dispatcher const& dispatcher,
    typename details::dispatcher_traits<Dispatcher>::Priority priority = details::dispatcher_traits<Dispatcher>::Priority::Normal)
{
    using Traits = details::dispatcher_traits<Dispatcher>;
    using Priority = typename Traits::Priority;
    using Handler = typename Traits::Handler;

    struct awaitable
    {
        awaitable(Dispatcher const& dispatcher, Priority priority) noexcept : m_dispatcher(dispatcher), m_priority(priority)
        {
        }
        bool await_ready() const noexcept
        {
            return false;
        }

        void await_suspend(details::coroutine_handle<> handle)
        {
            m_state.handle = handle;
            Handler handler{details::dispatcher_handler(&m_state)};
            try
            {
                // The return value of Schedule is not reliable. Use the dispatcher_handler destructor
                // to detect whether the work item failed to run.
                Traits::Scheduler::Schedule(m_dispatcher, m_priority, handler);
            }
            catch (...)
            {
                m_state.handle = nullptr; // the exception will resume the coroutine, so the handler shouldn't do it
                throw;
            }
        }

        void await_resume() const
        {
            if (m_state.orphaned)
            {
                throw winrt::hresult_error(static_cast<winrt::hresult>(0x800701ab)); // HRESULT_FROM_WIN32(ERROR_NO_TASK_QUEUE)
            }
        }

    private:
        Dispatcher const& m_dispatcher;
        Priority const m_priority;
        details::dispatched_handler_state m_state;
    };
    return awaitable{dispatcher, priority};
}
} // namespace wil
#endif // Coroutines are supported

#endif // __WIL_CPPWINRT_HELPERS_DEFINED

/// @cond
#if defined(WINRT_Windows_UI_Core_H) && !defined(__WIL_CPPWINRT_WINDOWS_UI_CORE_HELPERS)
#define __WIL_CPPWINRT_WINDOWS_UI_CORE_HELPERS
namespace wil::details
{
template <>
struct dispatcher_traits<winrt::Windows::UI::Core::CoreDispatcher>
{
    using Priority = winrt::Windows::UI::Core::CoreDispatcherPriority;
    using Handler = winrt::Windows::UI::Core::DispatchedHandler;
    using Scheduler = dispatcher_RunAsync;
};
} // namespace wil::details
#endif // __WIL_CPPWINRT_WINDOWS_UI_CORE_HELPERS

#if defined(WINRT_Windows_System_H) && !defined(__WIL_CPPWINRT_WINDOWS_SYSTEM_HELPERS)
#define __WIL_CPPWINRT_WINDOWS_SYSTEM_HELPERS
namespace wil::details
{
template <>
struct dispatcher_traits<winrt::Windows::System::DispatcherQueue>
{
    using Priority = winrt::Windows::System::DispatcherQueuePriority;
    using Handler = winrt::Windows::System::DispatcherQueueHandler;
    using Scheduler = dispatcher_TryEnqueue;
};
} // namespace wil::details
#endif // __WIL_CPPWINRT_WINDOWS_SYSTEM_HELPERS

#if defined(WINRT_Microsoft_System_H) && !defined(__WIL_CPPWINRT_MICROSOFT_SYSTEM_HELPERS)
#define __WIL_CPPWINRT_MICROSOFT_SYSTEM_HELPERS
namespace wil::details
{
template <>
struct dispatcher_traits<winrt::Microsoft::System::DispatcherQueue>
{
    using Priority = winrt::Microsoft::System::DispatcherQueuePriority;
    using Handler = winrt::Microsoft::System::DispatcherQueueHandler;
    using Scheduler = dispatcher_TryEnqueue;
};
} // namespace wil::details
#endif // __WIL_CPPWINRT_MICROSOFT_SYSTEM_HELPERS

#if defined(WINRT_Microsoft_UI_Dispatching_H) && !defined(__WIL_CPPWINRT_MICROSOFT_UI_DISPATCHING_HELPERS)
#define __WIL_CPPWINRT_MICROSOFT_UI_DISPATCHING_HELPERS
namespace wil::details
{
template <>
struct dispatcher_traits<winrt::Microsoft::UI::Dispatching::DispatcherQueue>
{
    using Priority = winrt::Microsoft::UI::Dispatching::DispatcherQueuePriority;
    using Handler = winrt::Microsoft::UI::Dispatching::DispatcherQueueHandler;
    using Scheduler = dispatcher_TryEnqueue;
};
} // namespace wil::details
#endif // __WIL_CPPWINRT_MICROSOFT_UI_DISPATCHING_HELPERS
/// @endcond

#if (defined(WINRT_Windows_Foundation_Collections_H) && !defined(__WIL_CPPWINRT_WINDOWS_FOUNDATION_COLLECTION_HELPERS)) || \
    defined(WIL_DOXYGEN)
/// @cond
#define __WIL_CPPWINRT_WINDOWS_FOUNDATION_COLLECTION_HELPERS
/// @endcond
namespace wil
{
/// @cond
namespace details
{
    template <typename T>
    struct is_winrt_vector_like
    {
    private:
        template <
            typename U,
            typename = decltype(std::declval<U>().GetMany(std::declval<U>().Size(), winrt::array_view<decltype(std::declval<U>().GetAt(0))>{}))>
        static constexpr bool get_value(int)
        {
            return true;
        }
        template <typename>
        static constexpr bool get_value(...)
        {
            return false;
        }

    public:
        static constexpr bool value = get_value<T>(0);
    };

    template <typename T>
    struct is_winrt_iterator_like
    {
    private:
        template <typename U, typename = decltype(std::declval<U>().GetMany(winrt::array_view<decltype(std::declval<U>().Current())>{}))>
        static constexpr bool get_value(int)
        {
            return true;
        }
        template <typename>
        static constexpr bool get_value(...)
        {
            return false;
        }

    public:
        static constexpr bool value = get_value<T>(0);
    };

    template <typename T>
    constexpr T empty() noexcept
    {
        if constexpr (std::is_base_of_v<winrt::Windows::Foundation::IUnknown, T>)
        {
            return nullptr;
        }
        else
        {
            return {};
        }
    }
} // namespace details
/// @endcond

/** Converts C++ / WinRT vectors, iterators, and iterables to std::vector by requesting the
collection's data in bulk. This can be more efficient in terms of IPC cost than iteratively
processing the collection.
@code
winrt::IVector<winrt::hstring> collection = GetCollection();
std::vector<winrt::hstring> allData = wil::to_vector(collection); // read all data from collection
for (winrt::hstring const& item : allData)
{
    // use item
}
@endcode
Can be used for IVector<T>, IVectorView<T>, IIterable<T>, IIterator<T>, and any type or
interface that C++/WinRT projects those interfaces for (PropertySet, IMap<T,K>, etc.)
Iterable-only types fetch content in units of 64. When used with an iterator, the returned
vector contains the iterator's current position and any others after it.
*/
template <typename TSrc>
auto to_vector(TSrc const& src)
{
    if constexpr (details::is_winrt_vector_like<TSrc>::value)
    {
        using T = decltype(src.GetAt(0));
        std::vector<T> result;
        if (auto expected = src.Size())
        {
            result.resize(expected + 1, details::empty<T>());
            auto actual = src.GetMany(0, result);
            if (actual > expected)
            {
                throw winrt::hresult_changed_state();
            }
            result.resize(actual, details::empty<T>());
        }
        return result;
    }
    else if constexpr (details::is_winrt_iterator_like<TSrc>::value)
    {
        using T = decltype(src.Current());
        std::vector<T> result;
        constexpr uint32_t chunkSize = 64;
        while (true)
        {
            auto const lastSize = result.size();
            result.resize(lastSize + chunkSize, details::empty<T>());
            auto fetched = src.GetMany({result.data() + lastSize, result.data() + lastSize + chunkSize});
            if (fetched < chunkSize)
            {
                result.resize(lastSize + fetched, details::empty<T>());
                break;
            }
        }
        return result;
    }
    else
    {
        return to_vector(src.First());
    }
}
} // namespace wil
#endif

#if (defined(WINRT_Windows_UI_H) && defined(_WINDOWS_UI_INTEROP_H_) && !defined(__WIL_CPPWINRT_WINDOWS_UI_INTEROP_HELPERS)) || \
    defined(WIL_DOXYGEN)
/// @cond
#define __WIL_CPPWINRT_WINDOWS_UI_INTEROP_HELPERS
/// @endcond
#if !defined(____x_ABI_CWindows_CFoundation_CIClosable_FWD_DEFINED__) && !defined(MIDL_NS_PREFIX)
#pragma push_macro("ABI")
#undef ABI
#define ABI
#endif

namespace wil
{
#if defined(NTDDI_VERSION) && (NTDDI_VERSION >= NTDDI_WIN10_CU)
//! The following methods require that you include both <winrt/Windows.UI.h>
//! <Windows.UI.Interop.h> before including wil/cppwinrt_helpers.h, and that NTDDI_VERSION
//! is at least NTDDI_WIN10_CU. It is okay to include wil/cppwinrt_helpers.h multiple times:
//! support will be enabled for any headers that were included since the previous inclusion
//! of wil/cppwinrt_headers.h.
inline winrt::Windows::UI::WindowId GetWindowIdFromWindow(HWND hwnd)
{
    ABI::Windows::UI::WindowId abiWindowId;
    winrt::check_hresult(::GetWindowIdFromWindow(hwnd, &abiWindowId));
    return winrt::Windows::UI::WindowId{abiWindowId.Value};
}

inline HWND GetWindowFromWindowId(winrt::Windows::UI::WindowId windowId)
{
    HWND hwnd;
    winrt::check_hresult(::GetWindowFromWindowId({windowId.Value}, &hwnd));
    return hwnd;
}
#endif /*defined(NTDDI_VERSION) && (NTDDI_VERSION >= NTDDI_WIN10_CU)*/
} // namespace wil

#if !defined(____x_ABI_CWindows_CFoundation_CIClosable_FWD_DEFINED__) && !defined(MIDL_NS_PREFIX)
#pragma pop_macro("ABI")
#endif
#endif // __WIL_CPPWINRT_WINDOWS_UI_INTEROP_HELPERS
